home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 1.0 beta / flock-1.0RC3.en-US.win32.exe / flock / components / flockOpmlService.js < prev    next >
Text File  |  2007-10-18  |  15KB  |  528 lines

  1. // vim: tabstop=2 softtabstop=2 shiftwidth=2 expandtab
  2. //
  3. // BEGIN FLOCK GPL
  4. // 
  5. // Copyright Flock Inc. 2005-2007
  6. // http://flock.com
  7. // 
  8. // This file may be used under the terms of of the
  9. // GNU General Public License Version 2 or later (the "GPL"),
  10. // http://www.gnu.org/licenses/gpl.html
  11. // 
  12. // Software distributed under the License is distributed on an "AS IS" basis,
  13. // WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  14. // for the specific language governing rights and limitations under the
  15. // License.
  16. // 
  17. // END FLOCK GPL
  18. //
  19.  
  20. const OPML_CONTRACTID = '@flock.com/opml-service;1';
  21. const OPML_CLASSID    = Components.ID('{a3948322-0bb8-413c-8c6d-b0959d6d5bad}');
  22. const OPML_CLASSNAME  = 'Flock OPML Service';
  23.  
  24. const MARK_READ_CONTRACTID = '@flock.com/opml-subscribe-listener-mark-read;1';
  25. const MARK_READ_CLASSID    = Components.ID('{4cb09bdd-cca3-4f8d-86b4-d4b315c42b45}');
  26. const MARK_READ_CLASSNAME  = 'Flock OPML Mark Read on Subscribe Listener';
  27.  
  28.  
  29. const Cc = Components.classes;
  30. const Ci = Components.interfaces;
  31. const Cr = Components.results;
  32.  
  33. const XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n'
  34.  
  35. const MAX_OPML_DEPTH = 20;
  36.  
  37. /* from nspr's prio.h */
  38. const PR_RDONLY      = 0x01;
  39. const PR_WRONLY      = 0x02;
  40. const PR_RDWR        = 0x04;
  41. const PR_CREATE_FILE = 0x08;
  42. const PR_APPEND      = 0x10;
  43. const PR_TRUNCATE    = 0x20;
  44. const PR_SYNC        = 0x40;
  45. const PR_EXCL        = 0x80;
  46.  
  47.  
  48. function writeXML(xml, file) {
  49.   var xmlStr = XML_HEADER + xml.toXMLString();
  50.  
  51.   if (file.exists())
  52.     file.remove(false);
  53.  
  54.   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
  55.  
  56.   var ostream = Cc['@mozilla.org/network/file-output-stream;1']
  57.     .createInstance(Ci.nsIFileOutputStream);
  58.  
  59.   ostream.init(file, PR_WRONLY | PR_CREATE_FILE, 0644, 0);
  60.  
  61.   var converter = Cc['@mozilla.org/intl/scriptableunicodeconverter']
  62.     .createInstance(Ci.nsIScriptableUnicodeConverter);
  63.   converter.charset = 'UTF-8';
  64.  
  65.   var convdata = converter.ConvertFromUnicode(xmlStr) + converter.Finish();
  66.  
  67.   ostream.write(convdata, convdata.length);
  68.  
  69.   ostream.flush();
  70.   ostream.close();
  71. }
  72.  
  73.  
  74. function OpmlRequest(url, folder, flatRoot, listener, ctxt, logger) {
  75.   this._url      = url;
  76.   this._folder   = folder;
  77.   this._flatRoot = flatRoot;
  78.   this._listener = listener;
  79.   this._ctxt     = ctxt;
  80.   this._logger   = logger;
  81.  
  82.   var ios = Cc['@mozilla.org/network/io-service;1']
  83.     .getService(Ci.nsIIOService);
  84.   this._channel = ios.newChannelFromURI(url, null, null);
  85. }
  86.  
  87. OpmlRequest.prototype = {
  88.   get: function OR_get() {
  89.     this._channel.asyncOpen(this, null);
  90.   },
  91.  
  92.   onStartRequest: function OR_onStartRequest(request, context) {
  93.     this._xml = '';
  94.   },
  95.   onStopRequest: function OR_onStopRequest(request, context, status) {
  96.     if (Components.isSuccessCode(status))
  97.       this._subscribe();
  98.     else if (this._listener)
  99.       this._listener.onLoadError(this._ctxt, null);
  100.   },
  101.   onDataAvailable: function OR_onDataAvailable(request, context, inputStream,
  102.                                                sourceOffset, count) {
  103.     var sstream = Cc['@mozilla.org/scriptableinputstream;1']
  104.       .createInstance(Ci.nsIScriptableInputStream);
  105.     sstream.init(inputStream);
  106.  
  107.     this._xml += sstream.read(count);
  108.   },
  109.  
  110.   _sendLoadError: function OR__sendLoadError(errorCode) {
  111.     if (this._listener) {
  112.       var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  113.       error.errorCode = errorCode;
  114.       this._listener.onLoadError(this._ctxt, error);
  115.     }
  116.   },
  117.  
  118.   _subscribe: function OR__subscribe() {
  119.     var url = this._url.spec;
  120.     this._logger.info('got OPML data from ' + url);
  121.  
  122.     try {
  123.       var xmlData = new XML(this._xml.replace(/<\?xml[^?]*\?>/, ''));
  124.     }
  125.     catch (e) {
  126.       this._logger.error('Invalid XML at ' + url);
  127.       this._sendLoadError(Ci.flockIError.OPML_INVALID_XML);
  128.       return;
  129.     }
  130.  
  131.     var body = xmlData.body;
  132.  
  133.     if (xmlData.name() != 'opml' || !body) {
  134.       this._logger.error('No OPML data at ' + url);
  135.       this._sendLoadError(Ci.flockIError.OPML_NO_DATA);
  136.       return;
  137.     }
  138.  
  139.     var root;
  140.     if (!this._flatRoot) {
  141.       var title = xmlData.head.title;
  142.       if (title && title.toString().length > 0)
  143.         root = [xmlData.head.title.toString()];
  144.       else
  145.         root = ['Imported Feeds'];
  146.     } else {
  147.       root = [this._folder];
  148.     }
  149.  
  150.     this._feeds = [];
  151.     this._tooDeep = false;
  152.  
  153.     this._getFeeds(root, body);
  154.  
  155.     if (this._tooDeep) {
  156.       this._logger.error('Hierarchy at ' + url + ' is too deep');
  157.       this._sendLoadError(Ci.flockIError.OPML_TOO_DEEP);
  158.       return;
  159.     }
  160.  
  161.     if (this._feeds.length == 0) {
  162.       this._logger.error('No feeds in OPML at ' + url);
  163.       this._sendLoadError(Ci.flockIError.OPML_NO_FEEDS);
  164.       return;
  165.     }
  166.  
  167.     this._logger.info(this._feeds.length + ' feeds found in ' + url);
  168.     this._subscribeFeeds();
  169.   },
  170.  
  171.   _getFeeds: function OR__getFeeds(hierarchy, node) {
  172.     for each (var outline in node.outline) {
  173.       var xmlUrl = outline.@xmlUrl;
  174.       if (xmlUrl && xmlUrl.toString().length > 0 &&
  175.           !outline.hasComplexContent()) {
  176.         var feed = { hierarchy: hierarchy,
  177.                      url: xmlUrl.toString(),
  178.                      title: null
  179.                    };
  180.  
  181.         text = outline.@text;
  182.         if (!text || text.toString().length == 0)
  183.           text = outline.@title;
  184.  
  185.         if (text && text.toString().length > 0)
  186.           feed.title = text.toString();
  187.  
  188.         this._logger.info('Found feed ' + feed.url);
  189.         this._feeds.push(feed);
  190.       } else {
  191.         var title = outline.@title;
  192.         if (!title || title.toString().length == 0)
  193.           title = outline.@text;
  194.  
  195.         if (title && title.toString().length > 0) {
  196.           var newHierarchy = hierarchy.concat(title.toString());
  197.           if (newHierarchy.length > MAX_OPML_DEPTH) {
  198.             this._tooDeep = true;
  199.             return;
  200.           }
  201.  
  202.           this._logger.info('Found folder ' + title.toString());
  203.  
  204.           if (outline.hasComplexContent()) {
  205.             this._getFeeds(newHierarchy, outline);
  206.           } else {
  207.             var feed = { hierarchy: newHierarchy,
  208.                          url: null
  209.                        };
  210.             this._feeds.push(feed);
  211.           }
  212.  
  213.           if (this._tooDeep)
  214.             return;
  215.         }
  216.       }
  217.     }
  218.   },
  219.  
  220.   _subscribeFeeds: function OR__subscribeFeeds(folder, node) {
  221.     var fm = Cc['@flock.com/feed-manager;1']
  222.       .getService(Ci.flockIFeedManager);
  223.  
  224.     var ios = Cc['@mozilla.org/network/io-service;1']
  225.       .getService(Ci.nsIIOService);
  226.  
  227.     var subscriber = {
  228.       _feeds:    this._feeds,
  229.       
  230.       _listener: this._listener,
  231.       _ctxt:     this._ctxt,
  232.       _logger:   this._logger,
  233.       _url:      this._url,
  234.  
  235.       _index:    -1,
  236.  
  237.       get _feed() { return this._feeds[this._index]; },
  238.  
  239.       createFolderHierarchy: function(hierarchy) {
  240.         var destFolder = null;
  241.         for each (var folder in hierarchy) {
  242.           if (folder instanceof Ci.flockIFeedFolder) {
  243.             destFolder = folder;
  244.           } else {
  245.             var newFolder;
  246.             try {
  247.               newFolder = destFolder.addFolder(folder);
  248.               this._logger.info('Created folder: ' + folder);
  249.             }
  250.             catch (e) {
  251.               newFolder = destFolder.getChildFolder(folder);
  252.             }
  253.             destFolder = newFolder;
  254.           }
  255.         }
  256.         return destFolder;
  257.       },
  258.  
  259.       onGetFeedComplete: function(feed) {
  260.         if (this._listener)
  261.           this._listener.onGetFeed(feed, this._ctxt);
  262.  
  263.         var destFolder = this.createFolderHierarchy(this._feed.hierarchy);
  264.  
  265.         this._feed.hierarchy.length = 0;
  266.         this._feed.hierarchy.push(destFolder);
  267.  
  268.         this._logger.info('Subscribing ' + this._feed.url + ' to folder ' +
  269.                           destFolder.getTitle());
  270.         destFolder.subscribeFeed(feed);
  271.         Cc["@flock.com/metrics-service;1"]
  272.           .getService(Ci.flockIMetricsService)
  273.           .report("FeedsSidebar-AddFeed", "OpmlImport");
  274.  
  275.         if (this._listener)
  276.           this._listener.onSubscribe(feed, this._ctxt,
  277.                                      this._index, this._feeds.length);
  278.  
  279.         this.processNextFeed();
  280.       },
  281.       onError: function(error) {
  282.         this._logger.warn('Error fetching ' + this._feed.url);
  283.  
  284.         if (this._listener)
  285.           this._listener.onError(this._feed.url, this._ctxt, error,
  286.                                  this._index, this._feeds.length);
  287.  
  288.         this.processNextFeed();
  289.       },
  290.  
  291.       processNextFeed: function() {
  292.         this._index++;
  293.  
  294.         while (this._index < this._feeds.length) {
  295.           if (this._feed.url == null) {
  296.             this.createFolderHierarchy(this._feed.hierarchy);
  297.             this._index++;
  298.             continue;
  299.           }
  300.  
  301.           var url = null;
  302.           try {
  303.             url = ios.newURI(this._feed.url, null, null);
  304.           }
  305.           catch (e) { }
  306.  
  307.           if (url) {
  308.             this._logger.info('Fetching feed ' + this._feed.url);
  309.             fm.getFeed(url, this);
  310.             return;
  311.           }
  312.  
  313.           if (this._listener) {
  314.             this._logger.warn('Bad feed URL: ' + this._feed.url);
  315.             var error = Cc['@flock.com/error;1'].createInstance(Ci.flockIError);
  316.             error.errorCode = error.OPML_BAD_FEED_URL;
  317.             this._listener.onError(this._feed.url, this._ctxt, error,
  318.                                    this._index, this._feeds.length);
  319.           }
  320.  
  321.           this._index++;
  322.         }
  323.  
  324.         this._logger.info('Finished OPML subscription of ' + this._url.spec);
  325.  
  326.         if (this._listener)
  327.           this._listener.onFinish(this._ctxt);
  328.       }
  329.     }
  330.  
  331.     subscriber.processNextFeed();
  332.   },
  333.  
  334.   QueryInterface: function OR_QueryInterface(iid) {
  335.     if (iid.equals(Ci.nsIStreamListener) ||
  336.         iid.equals(Ci.nsIRequestObserver)||
  337.         iid.equals(Ci.nsISupports))
  338.       return this;
  339.     throw Cr.NS_ERROR_NO_INTERFACE;
  340.   },
  341. }
  342.  
  343.  
  344. function OpmlService() {
  345.   this._logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  346.   this._logger.init('opmlservice');
  347.   this._logger.info('created');
  348. }
  349.  
  350. OpmlService.prototype = {
  351.   subscribe: function OPML_subscribe(url, folder, flatRoot, listener, ctxt) {
  352.     this._logger.info('subscribing OPML from ' + url);
  353.  
  354.     var req = new OpmlRequest(url, folder, flatRoot, listener, ctxt, this._logger);
  355.     req.get();
  356.   },
  357.  
  358.   export: function OPML_export(folder, title, file) {
  359.     var opml =
  360. <opml version="1.1">
  361. <head>
  362.   <title>{title}</title>
  363. </head>
  364. <body/>
  365. </opml>;
  366.  
  367.     this._exportFolder(folder, opml.body);
  368.  
  369.     writeXML(opml, file);
  370.   },
  371.  
  372.   _exportFolder: function OPML__exportFolder(folder, parentNode) {
  373.     var children = folder.getChildren();
  374.  
  375.     while (children && children.hasMoreElements()) {
  376.       var child = children.getNext();
  377.       var node = <outline/>;
  378.  
  379.       if (child instanceof Ci.flockIFeedFolder) {
  380.         node.@text = child.getTitle();
  381.         this._exportFolder(child, node);
  382.       } else {
  383.         node.@type = 'rss';
  384.         node.@text = child.getTitle();
  385.         node.@title = child.getTitle();
  386.         node.@xmlUrl = child.getURL().spec;
  387.       }
  388.  
  389.       parentNode.outline += node;
  390.     }
  391.   },
  392.  
  393.   getInterfaces: function OPML_getInterfaces(countRef) {
  394.     var interfaces = [Ci.flockIOpmlService, Ci.nsIClassInfo, Ci.nsISupports];
  395.     countRef.value = interfaces.length;
  396.     return interfaces;
  397.   },
  398.   getHelperForLanguage: function OPML_getHelperForLanguage(language) {
  399.     return null;
  400.   },
  401.   contractID: OPML_CONTRACTID,
  402.   classDescription: OPML_CLASSNAME,
  403.   classID: OPML_CLASSID,
  404.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  405.   flags: Ci.nsIClassInfo.SINGLETON,
  406.  
  407.   QueryInterface: function OPML_QueryInterface(iid) {
  408.     if (iid.equals(Ci.flockIOpmlService) ||
  409.         iid.equals(Ci.nsIClassInfo) ||
  410.         iid.equals(Ci.nsISupports))
  411.       return this;
  412.     throw Cr.NS_ERROR_NO_INTERFACE;
  413.   }
  414. }
  415.  
  416.  
  417. function MarkReadListener() {
  418. }
  419.  
  420. MarkReadListener.prototype = {
  421.   onSubscribe: function MARK_READ_onSubscribe(feed, ctxt, progress, progressMax) {
  422.   },
  423.   onFinish: function MARK_READ_onFinish(ctxt) {
  424.   },
  425.   onLoadError: function MARK_READ_onLoadError(ctxt, error) {
  426.   },
  427.   onError: function MARK_READ_onError(url, ctxt, error, progress, progressMax) {
  428.   },
  429.   onGetFeed: function MARK_READ_onGetFeed(feed, ctxt) {
  430.     feed.markRead();
  431.   },
  432.  
  433.   getInterfaces: function MARK_READ_getInterfaces(countRef) {
  434.     var interfaces = [Ci.flockIOpmlSubscribeListener, Ci.nsIClassInfo,
  435.                       Ci.nsISupports];
  436.     countRef.value = interfaces.length;
  437.     return interfaces;
  438.   },
  439.   getHelperForLanguage: function MARK_READ_getHelperForLanguage(language) {
  440.     return null;
  441.   },
  442.   contractID: MARK_READ_CONTRACTID,
  443.   classDescription: MARK_READ_CLASSNAME,
  444.   classID: MARK_READ_CLASSID,
  445.   implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
  446.   flags: Ci.nsIClassInfo.SINGLETON,
  447.  
  448.   QueryInterface: function MARK_READ_QueryInterface(iid) {
  449.     if (iid.equals(Ci.flockIOpmlSubscribeListener) ||
  450.         iid.equals(Ci.nsIClassInfo) ||
  451.         iid.equals(Ci.nsISupports))
  452.       return this;
  453.     throw Cr.NS_ERROR_NO_INTERFACE;
  454.   }
  455. }
  456.  
  457.  
  458. function GenericComponentFactory(ctor) {
  459.   this._ctor = ctor;
  460. }
  461.  
  462. GenericComponentFactory.prototype = {
  463.  
  464.   _ctor: null,
  465.  
  466.   // nsIFactory
  467.   createInstance: function(outer, iid) {
  468.     if (outer != null)
  469.       throw Cr.NS_ERROR_NO_AGGREGATION;
  470.     return (new this._ctor()).QueryInterface(iid);
  471.   },
  472.  
  473.   // nsISupports
  474.   QueryInterface: function(iid) {
  475.     if (iid.equals(Ci.nsIFactory) ||
  476.         iid.equals(Ci.nsISupports))
  477.       return this;
  478.     throw Cr.NS_ERROR_NO_INTERFACE;
  479.   },
  480. };
  481.  
  482. var Module = {
  483.   QueryInterface: function(iid) {
  484.     if (iid.equals(Ci.nsIModule) ||
  485.         iid.equals(Ci.nsISupports))
  486.       return this;
  487.  
  488.     throw Cr.NS_ERROR_NO_INTERFACE;
  489.   },
  490.  
  491.   getClassObject: function(cm, cid, iid) {
  492.     if (!iid.equals(Ci.nsIFactory))
  493.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  494.  
  495.     if (cid.equals(OPML_CLASSID))
  496.       return new GenericComponentFactory(OpmlService)
  497.     if (cid.equals(MARK_READ_CLASSID))
  498.       return new GenericComponentFactory(MarkReadListener)
  499.  
  500.     throw Cr.NS_ERROR_NO_INTERFACE;
  501.   },
  502.  
  503.   registerSelf: function(cm, file, location, type) {
  504.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  505.     cr.registerFactoryLocation(OPML_CLASSID, OPML_CLASSNAME,
  506.                                OPML_CONTRACTID,
  507.                                file, location, type);
  508.     cr.registerFactoryLocation(MARK_READ_CLASSID, MARK_READ_CLASSNAME,
  509.                                MARK_READ_CONTRACTID,
  510.                                file, location, type);
  511.   },
  512.  
  513.   unregisterSelf: function(cm, location, type) {
  514.     var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
  515.     cr.unregisterFactoryLocation(OPML_CLASSID, location);
  516.     cr.unregisterFactoryLocation(MARK_READ_CLASSID, location);
  517.   },
  518.  
  519.   canUnload: function(cm) {
  520.     return true;
  521.   },
  522. };
  523.  
  524. function NSGetModule(compMgr, fileSpec)
  525. {
  526.   return Module;
  527. }
  528.